package ee.parameterConverter.impl;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ee.parameterConverter.ConversionFailedException;
import ee.parameterConverter.Converter;
import ee.parameterConverter.ConverterResolver;
import ee.parameterConverter.EmptyValueException;
import ee.parameterConverter.MapConverter;
import ee.parameterConverter.ValidationResultCollector;

public class MapConverterImpl implements MapConverter {

	private ParameterConverterImpl _parameterConverter;
	private ConverterResolver _converterResolver;
	private ValidationResultCollector _validationResultCollector;
	
	public MapConverterImpl(ParameterConverterImpl parameterConverter, ConverterResolver converterResolver, ValidationResultCollector validationResultCollector)
	{
		_parameterConverter = parameterConverter;
		_converterResolver = converterResolver;
		_validationResultCollector = validationResultCollector;
	}
	
	@Override
	public Object convert(Map<String, String[]> parameters, Type type)
			throws ConversionFailedException, EmptyValueException {
		return convert(parameters, type, null);
	}

	@Override
	public Object convert(Map<String, String[]> parameters, Type type, String parameterKey)
			throws ConversionFailedException, EmptyValueException {
		
		// see if we have a converter
		Converter<Map<String, String[]>, ?> converter = _converterResolver.getParameterConverter(type);

		if (converter != null) {
			return converter.convert(parameters);
		}
		
		ArrayList<String> parameterPath = new ArrayList<String>();
		
		if (parameterKey != null)
		{
			parameterPath.add(parameterKey);
		}
		
		Map<String, Object> normalizedParameters = _parameterConverter.normalizeParameters(parameters, parameterKey);
		
		if (parameterKey != null)
		{
			if (normalizedParameters.containsKey(parameterKey))
			{
				@SuppressWarnings("unchecked")
				Map<String, Object> newParameters = (Map<String, Object>) normalizedParameters.get(parameterKey);
				normalizedParameters = newParameters;
			} else
			{
				throw new ConversionFailedException("Could not find parameter key '" + parameterKey + "' in parameters", parameters);
			}
		}
		
		return convert(normalizedParameters, type, parameterPath);
	}

	@Override
	public Object convert(Map<String, Object> normalizedParameters, Type type,
			List<String> parameterPath) throws ConversionFailedException {
		if (!(type instanceof ParameterizedType)) {
			throw new ConversionFailedException(
					"Could not convert to a Map type because no generic information is present. The given type should be an instance of ParameterizedType");
		}

		ParameterizedType parameterizedType = (ParameterizedType) type;

		Type[] typeArguments = parameterizedType.getActualTypeArguments();

		Type keyType = typeArguments[0];
		Type valueType = typeArguments[1];
		
		Map<Object, Object> result = new HashMap<Object, Object>();
		
		for (String key : normalizedParameters.keySet()) {
			
			Object value = normalizedParameters.get(key);
			
			ArrayList<String> currentParameterPath = new ArrayList<String>(parameterPath);
			currentParameterPath.add(key);
			
			try
			{
				if (value instanceof String[])
				{
					result.put(_parameterConverter.convert(key, keyType), _parameterConverter.convert((String[]) value, valueType));
				} else
				{
					@SuppressWarnings("unchecked")
					Map<String, Object> valueMap = (Map<String, Object>) value;
					result.put(_parameterConverter.convert(key, keyType), _parameterConverter.convert(valueMap, valueType, currentParameterPath));
				}
			} catch (Throwable e)
			{
				_validationResultCollector.processException(currentParameterPath, e);
			}
		}
		
		return result;
	}
	
	
}
